#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>

#include <err.h>
#include <sysexits.h>

#define ERR(e, r)		\
	do {			\
		errno = (e);	\
		return (r);	\
	} while (0)

#define XTOI(c)						\
	((c) >= '0' && (c) <= '9' ? (c) - '0' :		\
	((c) >= 'A' && (c) <= 'F' ? (c) - 'A' + 10 :	\
	((c) >= 'a' && (c) <= 'f' ? (c) - 'a' + 10 : 0)))

#define MAX(a, b) ((a) > (b) ? (a) : (b))

#define COLOR_INITIALIZER { { 0 }, 0, 0 }
struct color {
	uint8_t color[8];
	size_t length;
	uint32_t weight;
};

static int	 strtocol(const char *, struct color *);
static int	 strtou32(const char *, uint32_t *);
static void	*memsetb(void *, size_t, const void *, size_t);

int
main(int argc, char *argv[])
{
	uint8_t *b;
	struct color *cs;
	ssize_t ri;
	size_t bsize;
	size_t ncs;
	size_t wt;
	size_t i;
	size_t k;
	uint32_t w = 1080;
	uint32_t h = 720;
	uint32_t tw;
	uint32_t lw;
	uint32_t j;
	int vflag = 0;
	int c;

	while ((c = getopt(argc, argv, "h:vw:")) != -1)
		switch (c) {
		case 'h':
			if (strtou32(optarg, &h) != 0)
				err(EX_USAGE, "Invalid number: %s",
				    optarg);
			break;
		case 'v':
			vflag = 1;
			break;
		case 'w':
			if (strtou32(optarg, &w) != 0)
				err(EX_USAGE, "Invalid number: %s",
				    optarg);
			if (w > UINT32_MAX / 8)
				errc(EX_USAGE, ERANGE,
				    "Invalid number: %s", optarg);
			break;
		default:
			goto usage;
		}
	argc -= optind;
	argv += optind;
	if (argc == 0)
		goto usage;
	if ((cs = malloc(argc * sizeof(*cs))) == NULL)
		err(EX_OSERR, "Could not allocate the color table");
	for (ncs = 0, wt = 0, tw = 0; *argv != NULL; ncs++, argv++) {
		if (strtocol(*argv, &cs[ncs]) != 0)
			err(EX_USAGE, "Invalid color: %s", *argv);
		if (cs[ncs].weight > tw)
			tw = cs[ncs].weight;
		wt += cs[ncs].weight;
	}
	j = vflag ? w : h;
	for (i = 0; i < ncs; i++)
		cs[i].length = j / wt * cs[i].weight;
	/* Add additional lines to the middle stripes */
	for (j %= wt, i = ncs / 2, lw = tw; j > 0;
	    lw = (lw > 0 ? lw - 1 : 0)) {
		for (k = 0, ri = 0; k < ncs && j > 0; k++,
		    ri = -ri + (ri >= 0 ? -1 : 0)) {
			if (cs[i + ri].weight >= lw) {
				cs[i + ri].length++;
				j--;
			}
		}
	}
	for (j %= wt, i = ncs / 2 - j / 2; j > 0; j--, i++)
		cs[i].length++;
	bsize = MAX((size_t)w * 8, 16);
	if ((b = malloc(bsize)) == NULL)
		err(EX_OSERR, "Could not allocate the buffer");
	(void)memcpy(b, "farbfeld", 8);
	b[8] = (w >> 24) & 0xFF;
	b[9] = (w >> 16) & 0xFF;
	b[10] = (w >> 8) & 0xFF;
	b[11] = (w >> 0) & 0xFF;
	b[12] = (h >> 24) & 0xFF;
	b[13] = (h >> 16) & 0xFF;
	b[14] = (h >> 8) & 0xFF;
	b[15] = (h >> 0) & 0xFF;
	(void)write(STDOUT_FILENO, b, 16);
	if (vflag) {
		for (i = 0, j = 0; i < ncs; j += cs[i++].length * 8)
			(void)memsetb((char *)b + j, cs[i].length * 8,
			    cs[i].color, 8);
		for (i = 0; i < h; i++)
			(void)write(STDOUT_FILENO, b, w * 8);
	} else {
		for (i = 0; i < ncs; i++) {
			(void)memsetb(b, bsize, cs[i].color, 8);
			for (j = 0; j < cs[i].length; j++)
				(void)write(STDOUT_FILENO, b, w * 8);
		}
	}
	return (EX_OK);
usage:
	(void)fprintf(stderr,
	    "usage: ffstripe [-v] [-h height] [-w width] color ...\n");
	return (EX_USAGE);
}

int
strtocol(const char *s, struct color *c)
{
	char *p;
	size_t i;
	size_t l;
	double d;
	uint32_t u32;
	uint16_t u16;

	if (s == NULL || c == NULL)
		ERR(EFAULT, -1);
	c->color[6] = 0xFF;
	c->color[7] = 0xFF;
	if (strchr(s, ',') != NULL) {
		errno = 0;
		for (i = 0; i < 6 && *s != ':' && *s != '\0';
		    i += 2, s += p - s) {
			for (; isspace(*s) || *s == ','; s++)
				/* do nothing */;
			errno = 0;
			d = strtod(s, &p);
			if (errno == 0 && (d < 0 || d > 1))
				errno = ERANGE;
			if (errno == 0 && (s == p ||
			    (i < 4 && *s != ',') ||
			    (i >= 4 && *s != ':')))
				errno = EINVAL;
			if (errno != 0)
				return (-1);
			u16 = d * (double)UINT16_MAX;
			c->color[i * 2 + 0] = (u16 >> 8) & 0xFF;
			c->color[i * 2 + 1] = (u16 >> 0) & 0xFF;
		}
		if (i < 6)
			ERR(EINVAL, -1);
	} else {
		for (; isspace(*s); s++)
			/* do nothing */;
		for (l = 0; s[l] != '\0'; l++)
			if ((s[l] < '0' || s[l] > '9') &&
			    (s[l] < 'A' || s[l] > 'F') &&
			    (s[l] < 'a' || s[l] > 'f'))
				break;
		if (l % 3 != 0 || l > 12)
			ERR(EINVAL, -1);
		for (i = 0; i < l / 3; i++) {
			c->color[0] = (c->color[0] << 4) |
			    (c->color[1] >> 4);
			c->color[1] = (c->color[1] << 4) | XTOI(s[i]);
			c->color[2] = (c->color[2] << 4) |
			    (c->color[3] >> 4);
			c->color[3] = (c->color[3] << 4) |
			    XTOI(s[i + l / 3]);
			c->color[4] = (c->color[4] << 4) |
			    (c->color[5] >> 4);
			c->color[5] = (c->color[5] << 4) |
			    XTOI(s[i + l / 3 * 2]);
		}
		switch (l) {
		case 3:
			c->color[1] = (c->color[1] << 4) |
			    (c->color[1] & 0x0F);
			c->color[3] = (c->color[3] << 4) |
			    (c->color[3] & 0x0F);
			c->color[5] = (c->color[5] << 4) |
			    (c->color[5] & 0x0F);
			/* FALLTHROUGH */
		case 6:
			c->color[0] = c->color[1];
			c->color[2] = c->color[3];
			c->color[4] = c->color[5];
			break;
		case 9:
			c->color[0] = (c->color[0] << 4) |
			    (c->color[1] >> 4);
			c->color[1] = (c->color[1] << 4) |
			    (c->color[0] >> 4);
			c->color[2] = (c->color[2] << 4) |
			    (c->color[3] >> 4);
			c->color[3] = (c->color[3] << 4) |
			    (c->color[2] >> 4);
			c->color[4] = (c->color[4] << 4) |
			    (c->color[5] >> 4);
			c->color[5] = (c->color[5] << 4) |
			    (c->color[4] >> 4);
			break;
		}
		s += l;
	}
	if (*s == ':') {
		s++;
		errno = 0;
		if (strtou32(s, &u32) != 0)
			return (-1);
		c->weight = u32;
	} else
		c->weight = 1;
	return (0);
}

int
strtou32(const char *s, uint32_t *o)
{
	char *p;
	unsigned long ul;

	if (s == NULL || o == NULL)
		ERR(EFAULT, -1);
	errno = 0;
	if ((ul = strtoul(s, &p, 0)) > UINT32_MAX)
		errno = ERANGE;
	if (errno == 0 && (p == s || *p != '\0'))
		errno = EINVAL;
	*o = ul;
	return (errno == 0 ? 0 : -1);
}

void *
memsetb(void *dst, size_t dsz, const void *src, size_t ssz)
{
	size_t i;

	if (dst == NULL || src == NULL)
		ERR(EFAULT, NULL);
	if (ssz == 0)
		return (dst);
	for (i = 0; dsz > ssz; i += ssz, dsz -= ssz)
		(void)memcpy((char *)dst + i, src, ssz);
	if (dsz > 0)
		(void)memcpy((char *)dst + i, src, dsz);
	return (dst);
}
